/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2009 - 2012 Luca Mingardi.
 *
 * This file is part of jeeObserver.
 *
 * JeeObserver is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * JeeObserver is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package jeeobserver.server.database;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import jeeobserver.HttpSessionCoreElement;
import jeeobserver.server.HttpSessionParameters;
import jeeobserver.server.HttpSessionParameters.DeleteStatisticsParameter;
import jeeobserver.server.HttpSessionParameters.SearchElementsParameter;
import jeeobserver.server.HttpSessionParameters.SearchStatisticsParameter;
import jeeobserver.server.HttpSessionStatistics;
import jeeobserver.server.database.DatabaseFileElements.DatabaseElementEntity;
import jeeobserver.server.database.DatabaseFileStatistics.DatabaseStatisticsEntity;
import jeeobserver.server.HttpSessionStatistics.ContextStatistics;
import jeeobserver.server.HttpSessionStatistics.Element;
import jeeobserver.server.HttpSessionStatistics.ElementStatistics;
import jeeobserver.server.HttpSessionStatistics.ProjectStatistics;
import jeeobserver.server.JeeObserverServerContext;
import jeeobserver.server.RequestParameter;
import jeeobserver.server.Statistics.DateStatistics;
import jeeobserver.server.Statistics.NumberStatistics;
import jeeobserver.utilities.DatabaseUtilities;

class FileHttpSessionHandler {

	private static final String ELEMENTS_FILE_NAME = "http_session_elements";

	private static final String STATISTICS_FILE_NAME = "http_session_statistics";

	private String path;

	private int samplingPeriod;

	private DatabaseFileElements<HttpSessionElementEntity> fileElements;

	private DatabaseFileStatistics fileStatistics;

	//Chached indexes
	public FileHttpSessionHandler(String path, int samplingPeriod) throws IOException {

		this.path = path;

		this.samplingPeriod = samplingPeriod;

		//Create files (if not exists)
		this.fileElements = new DatabaseFileElements<HttpSessionElementEntity>(this.path + ELEMENTS_FILE_NAME, HttpSessionElementEntity.class);

		this.fileStatistics = new DatabaseFileStatistics(this.path + STATISTICS_FILE_NAME, HttpSessionStatisticsEntity.class, HttpSessionStatisticsEntity.BYTES_SIZE);
	}

	public synchronized void persistStatistics(List<HttpSessionCoreElement> invocationsList) throws IOException {

		if (invocationsList.isEmpty()) {
			return;
		}

		JeeObserverServerContext.logger.log(Level.FINER, "Persisting {0} Http Session statistics.", String.valueOf(invocationsList.size()));

		Map<Integer, DatabaseStatisticsEntity> statisticsMap = new HashMap<Integer, DatabaseStatisticsEntity>();

		Map<Integer, DatabaseElementEntity> elementsMap = new HashMap<Integer, DatabaseElementEntity>();


		//Iterate and group all invocations
		for (final HttpSessionCoreElement coreElement : invocationsList) {

			//Find statistics sampling date
			final Date samplingDate = DatabaseUtilities.calculateStatisticTimestamp(coreElement.getTimestamp(), this.samplingPeriod);

			//Insert into cache all elements
			HttpSessionElementEntity elementEntity = new HttpSessionElementEntity(coreElement.getContext(), coreElement.getProject());

			if (!elementsMap.containsKey(elementEntity.getElement().getId())) {
				elementsMap.put(elementEntity.getElement().getId(), elementEntity);
			}

			//Insert into cache all statistics
			HttpSessionStatisticsEntity statisticsEntity = new HttpSessionStatisticsEntity(elementEntity.getElement().getId(), samplingDate, coreElement);
			if (!statisticsMap.containsKey(statisticsEntity.getId())) {
				statisticsMap.put(statisticsEntity.getId(), statisticsEntity);
			} else {
				statisticsMap.get(statisticsEntity.getId()).add(statisticsEntity);
			}
		}

		//Persist cached elements into Database
		this.fileElements.merge(elementsMap);

		//Persist cached statistics into Database
		this.fileStatistics.persist(new HashSet<DatabaseStatisticsEntity>(statisticsMap.values()));
	}

	public synchronized ResultSetElement<HttpSessionStatistics.Element> searchElements(SearchElementsParameter parameters) throws IOException {

		JeeObserverServerContext.logger.log(Level.FINE, "Searching Http Session elements. {0}", parameters.toString());

		final ResultSetElement<HttpSessionStatistics.Element> resultList = new ResultSetElement<HttpSessionStatistics.Element>();

		this.fileElements.reset();

		HttpSessionElementEntity elementEntity;

		while ((elementEntity = (HttpSessionElementEntity) this.fileElements.read()) != null) {
			if (elementEntity.passFilter(parameters.getElementId(), parameters.getContext(), parameters.getProject())) {
				resultList.add(elementEntity.getElement());
			}
		}

		return resultList;
	}

	public synchronized ResultSetStatistics<HttpSessionStatistics> searchStatistics(SearchStatisticsParameter parameters) throws IOException {

		JeeObserverServerContext.logger.log(Level.FINE, "Searching Http Session statistics. {0}", parameters.toString());

		//Check groupBy parameter
		if (parameters.getGroupBy() != RequestParameter.GROUP_BY_CONTEXT && parameters.getGroupBy() != RequestParameter.GROUP_BY_CONTEXT_PROJECT) {
			throw new IOException("GroupBy: " + parameters.getGroupBy() + " parameter not valid.");
		}

		//Search elements
		ResultSetElement<Element> elements = this.searchElements(new SearchElementsParameter(parameters.getElementId(), parameters.getContext(), parameters.getProject()));

		//Group by
		Map<Integer, Element> elementsGroupedElements = new HashMap<Integer, Element>();
		Map<Integer, Integer> elementsGroupedIds = new HashMap<Integer, Integer>();

		for (Element element : elements) {

			int elementGroupedId = 0;

			if (parameters.getGroupBy() == RequestParameter.GROUP_BY_CONTEXT) {
				elementGroupedId = (element.getContext()).hashCode();
			} else if (parameters.getGroupBy() == RequestParameter.GROUP_BY_CONTEXT_PROJECT) {
				elementGroupedId = element.getId();
			}

			if (!elementsGroupedIds.containsKey(element.getId())) {
				elementsGroupedIds.put(element.getId(), elementGroupedId);
			}

			if (!elementsGroupedElements.containsKey(elementGroupedId)) {
				elementsGroupedElements.put(elementGroupedId, element);
			}
		}


		Map<Date, Map<Integer, HttpSessionStatistics>> statisticsPerPeriodMap = new HashMap<Date, Map<Integer, HttpSessionStatistics>>();

		long startTime = System.currentTimeMillis();

		List<HttpSessionStatisticsEntity> statisticsList = this.fileStatistics.search(elementsGroupedIds.keySet(), parameters.getDateFrom(), parameters.getDateTo());

		for (HttpSessionStatisticsEntity statisticEntity : statisticsList) {

//			System.out.println("HTTP SESSION ENTITY: " + statisticEntity.getDate() + " --> " + statisticEntity.getSessionsDestroyedStatistics().getAverage() + " + " + statisticEntity.getSessionsDestroyedTimeNanosStatistics().getAverage());

			//Get grouped id
			int elementGroupedId = elementsGroupedIds.get(statisticEntity.getElementId());

			//Get statistics sampling period
			Date samplingDate = DatabaseUtilities.calculateStatisticTimestamp(statisticEntity.getDate(), parameters.getSamplingPeriod());

			HttpSessionStatistics statistics = null;

			Element element = elementsGroupedElements.get(elementGroupedId);

			if (parameters.getGroupBy() == RequestParameter.GROUP_BY_CONTEXT) {
				statistics = new ContextStatistics(statisticEntity, element.getContext());
			} else if (parameters.getGroupBy() == RequestParameter.GROUP_BY_CONTEXT_PROJECT) {
				statistics = new ProjectStatistics(statisticEntity, element.getContext(), element.getProject());
			}

			if (!statisticsPerPeriodMap.containsKey(samplingDate)) {
				statisticsPerPeriodMap.put(samplingDate, new HashMap<Integer, HttpSessionStatistics>());
			}

			if (statisticsPerPeriodMap.get(samplingDate).containsKey(elementGroupedId)) {
				statisticsPerPeriodMap.get(samplingDate).get(elementGroupedId).add(statistics);
			} else {
				statisticsPerPeriodMap.get(samplingDate).put(elementGroupedId, statistics);
			}
		}

		long stopTime = System.currentTimeMillis();

		System.out.println("Time: " + (stopTime - startTime));

		final ResultSetStatistics<HttpSessionStatistics> resultList = new ResultSetStatistics<HttpSessionStatistics>(this.samplingPeriod);

		for (Date date : statisticsPerPeriodMap.keySet()) {
			Map<Integer, HttpSessionStatistics> statisticsMap = statisticsPerPeriodMap.get(date);

			for (int id : statisticsMap.keySet()) {
				HttpSessionStatistics statistics = statisticsMap.get(id);

				statistics.calculateTrendStatistics();

				resultList.add(statistics);
			}
		}

		return resultList;
	}

	public synchronized void deleteStatistics(DeleteStatisticsParameter parameters) throws IOException {

		JeeObserverServerContext.logger.log(Level.FINE, "Deleting Http Session statistics. {0}", parameters.toString());

		//Filter elements
		ResultSetElement<HttpSessionStatistics.Element> elements = searchElements(new HttpSessionParameters.SearchElementsParameter(parameters.getElementId(), parameters.getContext(), parameters.getProject()));

		Set<Integer> elementsSet = new HashSet<Integer>();

		for (HttpSessionStatistics.Element element : elements) {
			elementsSet.add(element.getId());
		}



		//Delete statistics
		this.fileStatistics.delete(elementsSet, parameters.getDateFrom(), parameters.getDateTo());


		Set<Integer> elementsToNotDeleteSet = new HashSet<Integer>();

		//Delete elements
		this.fileElements.reset();

		HttpSessionElementEntity elementEntity;

		while ((elementEntity = (HttpSessionElementEntity) this.fileElements.read()) != null) {

			if (elementEntity.getStatus() != DatabaseElementEntity.STATUS_DELETED) {
				if (!elementsToNotDeleteSet.contains(elementEntity.getElement().getId())) {
					this.fileElements.writePervious(elementEntity);
				}
			}
		}
	}

	public synchronized void close() throws IOException {
		this.fileStatistics.close();
		this.fileElements.close();
	}

	static class HttpSessionElementEntity extends DatabaseElementEntity {

		private static final long serialVersionUID = 40L;

		private Element element;

		public HttpSessionElementEntity(byte[] bytes) {

			super();

			ByteBuffer buffer = ByteBuffer.wrap(bytes);

			this.status = buffer.get();

			this.element = new Element(buffer.getInt(), DatabaseUtilities.getString(buffer, buffer.getInt()), DatabaseUtilities.getString(buffer, buffer.getInt()));
		}

		public HttpSessionElementEntity(String context, String path) {

			this.status = DatabaseElementEntity.STATUS_ACTIVE;
			this.element = new Element(context, path);
		}

		public byte[] getBytes() {
			ByteBuffer buffer = ByteBuffer.allocate(this.getSize());

			buffer.put(this.status);

			buffer.putInt(this.element.getId());

			buffer.putInt(this.element.getContext().getBytes().length);
			buffer.put(this.element.getContext().getBytes());

			buffer.putInt(this.element.getProject().getBytes().length);
			buffer.put(this.element.getProject().getBytes());

			return buffer.array();
		}

		public int getSize() {
			return (13 + element.getContext().getBytes().length + element.getProject().getBytes().length);
		}

		public Element getElement() {
			return element;
		}

		public boolean passFilter(Integer id, String context, String path) {

			if (this.status == DatabaseElementEntity.STATUS_DELETED) {
				return false;
			} else if (id != null && id.intValue() != this.element.getId()) {
				return false;
			} else if (context != null && !context.equals(this.element.getContext())) {
				return false;
			} else if (path != null && !path.equals(this.element.getProject())) {
				return false;
			}

			return true;
		}

		public boolean passFilter(String context, String project, String category, String name) {
			throw new UnsupportedOperationException("Not supported yet.");
		}
	}

	static class HttpSessionStatisticsEntity extends ElementStatistics implements DatabaseStatisticsEntity {

		private static final long serialVersionUID = 40L;

		public static final int BYTES_SIZE = 94 + 1;

		private transient byte status;

		public HttpSessionStatisticsEntity(ByteBuffer buffer) {
			this(buffer.get(), buffer.getInt(), new Date(buffer.getLong()), buffer.getInt(), buffer.slice());
		}

		public HttpSessionStatisticsEntity(byte[] bytes) {
			this(ByteBuffer.wrap(bytes));
		}

		public HttpSessionStatisticsEntity(byte status, int elementId, Date date, int count, ByteBuffer buffer) {

			super(elementId, date, count);

			this.status = status;

			this.dateStatistics = new DateStatistics(new Date(buffer.getLong()), new Date(buffer.getLong()));

			this.sessionsCreatedStatistics = FileDatabaseHandler.getNumberStatistics(buffer, this.date);
			this.sessionsDestroyedStatistics = FileDatabaseHandler.getNumberStatistics(buffer, this.date);

			boolean sessionsClosedNull = false;
			if (this.sessionsDestroyedStatistics.getSum() == 0) {
				sessionsClosedNull = true;
			}

			this.sessionsDestroyedTimeNanosStatistics = FileDatabaseHandler.getNumberStatistics(buffer, this.date, sessionsClosedNull);
		}

		public HttpSessionStatisticsEntity(int elementId, Date date, HttpSessionCoreElement coreElement) {

			super(elementId, date, 1);

			this.status = DatabaseStatisticsEntity.STATUS_ACTIVE;

			this.dateStatistics = new DateStatistics(coreElement.getTimestamp());

			if (coreElement.getTimeNanos() >= 0) {
				this.sessionsCreatedStatistics = new NumberStatistics(date, 0);
				this.sessionsDestroyedStatistics = new NumberStatistics(date, 1);
				this.sessionsDestroyedTimeNanosStatistics = new NumberStatistics(date, coreElement.getTimeNanos());
			} else {
				this.sessionsCreatedStatistics = new NumberStatistics(date, 1);
				this.sessionsDestroyedStatistics = new NumberStatistics(date, 0);
				this.sessionsDestroyedTimeNanosStatistics = new NumberStatistics();
			}
		}

		public byte[] getBytes() {

			ByteBuffer buffer = ByteBuffer.allocate(BYTES_SIZE);

			buffer.put(this.status);
			buffer.putInt(this.elementId);

			FileDatabaseHandler.fillBuffer(buffer, this);

			FileDatabaseHandler.fillBuffer(buffer, this.dateStatistics);

			FileDatabaseHandler.fillBuffer(buffer, this.sessionsCreatedStatistics);
			FileDatabaseHandler.fillBuffer(buffer, this.sessionsDestroyedStatistics);

			FileDatabaseHandler.fillBuffer(buffer, this.sessionsDestroyedTimeNanosStatistics);

			return buffer.array();
		}

		public int getSize() {
			return BYTES_SIZE;
		}

		public byte getStatus() {
			return status;
		}

		public void setStatus(byte status) {
			this.status = status;
		}

		public void add(DatabaseStatisticsEntity entity) {
			super.add((HttpSessionStatisticsEntity) entity);
		}
	}
}
